package com.bj58.mis.apm.server.kylinreceive.service.impl;

import com.bj58.mis.apm.server.core.util.MD5;
import com.bj58.mis.apm.server.kylinreceive.mapper.KylinLogTimesMapper;
import com.bj58.mis.apm.server.kylinreceive.model.*;
import com.bj58.mis.apm.server.kylinreceive.service.KylinSuggestService;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.util.*;

/**
 * @Author: wangxw
 * @Date: 2018/7/18 16:00
 */
@Service
@Transactional
public class KylinSuggestServiceImpl implements KylinSuggestService {

    private Logger logger = LoggerFactory.getLogger(this.getClass());

    private final String totalKey = "total";
    private final String totalDuration = "duration";
    private final String timesKey = "times";
    private final String groupTimesKey = "groupTimes";
    private final String filterTimesKey = "filterTimes";
    private final String groupsTimesKey = "groupsTimes";
    private final String cuboidTimesKey = "cuboidTimes";
    private final String lastQueryTimeKey = "lastQueryTime";
    private final String segmentQueryTimeKey = "segmentQueryTimes";

    @Resource
    private KylinLogTimesMapper logTimesMapper;

    /**
     * 处理日志统计情况：
     * 1.cube查询次数统计
     * 2.column查询次数统计
     * 3.group查询次数统计
     * 4.cuboid查询次数统计
     * @param logs
     */
    @Override
    public void updateQueryTimes(List<KylinLog> logs) {

        Map<String, Map<String, Map<String, Long>>> queryTimes = queryTimes(logs);

        saveOrUpdateQueryTimes(queryTimes);

        saveOrUpdateColumnTimes(queryTimes);

        saveOrUpdateGroupTimes(queryTimes);

        saveOrUpdateCuboidTimes(queryTimes);

        saveOrUpdateSegmentTimes(queryTimes);
    }

    private void saveOrUpdateQueryTimes(Map<String, Map<String, Map<String, Long>>> queryTimes) {
        List<KylinQueryTimesCube> list = new ArrayList<>();
        queryTimes.keySet().forEach(key -> {
            KylinQueryTimesCube qt = new KylinQueryTimesCube();
            qt.setCubeId(key);
            qt.setQueryTimes(queryTimes.get(qt.getCubeId()).get(this.timesKey).get(this.totalKey));
            qt.setEffectiveTimes(qt.getQueryTimes().doubleValue());
            qt.setQueryDuration(queryTimes.get(qt.getCubeId()).get(this.timesKey).get(this.totalDuration));
            qt.setLastQueryTime(new Date(getLong(queryTimes.get(qt.getCubeId()).get(this.lastQueryTimeKey), qt.getCubeId())));
            list.add(qt);
        });
        if(list.size() > 0) {
            logTimesMapper.saveOrUpdateCubeQueryTimes(list);
        }
    }

    private void saveOrUpdateColumnTimes(Map<String, Map<String, Map<String, Long>>> queryTimes) {
        List<KylinQueryTimesColumn> list = new ArrayList<>();
        queryTimes.keySet().forEach(cubeId -> {
            Map<String, Long> times = queryTimes.get(cubeId).get(this.timesKey);
            Map<String, Long> groupTimes = queryTimes.get(cubeId).get(this.groupTimesKey);
            Map<String, Long> filterTimes =queryTimes.get(cubeId).get(this.filterTimesKey);
            times.keySet().forEach(column -> {
                if(!this.totalKey.equals(column) && !this.totalDuration.equals(column)) {
                    KylinQueryTimesColumn ct = new KylinQueryTimesColumn();
                    ct.setCubeId(cubeId);
                    ct.setRowkey(column);
                    ct.setQueryTimes(getLong(times, column));
                    ct.setEffectiveTimes(ct.getQueryTimes().doubleValue());
                    ct.setGroupTimes(getLong(groupTimes, column));
                    ct.setFilterTimes(getLong(filterTimes, column));
                    ct.setLastQueryTime(new Date(getLong(queryTimes.get(ct.getCubeId()).get(this.lastQueryTimeKey), ct.getRowkey())));
                    list.add(ct);
                }
            });
        });
        if(list.size() > 0) {
            logTimesMapper.saveOrUpdateColumnQueryTimes(list);
        }
    }

    private void saveOrUpdateGroupTimes(Map<String, Map<String, Map<String, Long>>> queryTimes) {
        List<KylinQueryTimesGroup> groupList = new ArrayList<>();

        queryTimes.keySet().forEach(cubeId -> {
            Map<String, Long> groupMap = queryTimes.get(cubeId).get(this.groupsTimesKey);
            groupMap.keySet().forEach(key -> {
                KylinQueryTimesGroup group = new KylinQueryTimesGroup();
                group.setCubeId(cubeId);
                group.setGroupColumns(key);
                group.setMd5(MD5.encode(key));
                group.setQueryTimes(getLong(groupMap, key));
                group.setEffectiveTimes(group.getQueryTimes().doubleValue());
                group.setLastQueryTime(new Date(getLong(queryTimes.get(cubeId).get(this.lastQueryTimeKey), "groups:" + key)));
                groupList.add(group);
            });
        });
        if(groupList.size() > 0) {
            logTimesMapper.saveOrUpdateGroupQueryTimes(groupList);
        }
    }

    private void saveOrUpdateCuboidTimes(Map<String, Map<String, Map<String, Long>>> queryTimes) {
        List<KylinQueryTimesCuboid> cuboidTimes = new ArrayList<>();
        queryTimes.keySet().forEach(cubeId -> {
            Map<String, Long> cuboidMap = queryTimes.get(cubeId).get(this.cuboidTimesKey);
            cuboidMap.keySet().forEach(key -> {
                KylinQueryTimesCuboid cuboid = new KylinQueryTimesCuboid();
                cuboid.setCubeId(cubeId);
                cuboid.setCuboid(Long.parseLong(key));
                cuboid.setQueryTimes(getLong(cuboidMap, key));
                cuboid.setEffectiveTimes(cuboid.getQueryTimes().doubleValue());
                cuboid.setLastQueryTime(new Date(getLong(queryTimes.get(cubeId).get(this.lastQueryTimeKey), key)));
                cuboidTimes.add(cuboid);
            });
        });
        if(cuboidTimes.size() > 0) {
            logTimesMapper.saveOrUpdateCuboidQueryTimes(cuboidTimes);
        }
    }

    private void saveOrUpdateSegmentTimes(Map<String, Map<String, Map<String, Long>>> queryTimes) {
        List<KylinQueryTimesSegment> segmentTimes = new ArrayList<>();
        queryTimes.keySet().forEach(cubeId -> {
            Map<String, Long> segmentMap = queryTimes.get(cubeId).get(this.segmentQueryTimeKey);
            segmentMap.keySet().forEach(key -> {
                KylinQueryTimesSegment seg = new KylinQueryTimesSegment();
                seg.setSegmentId(key);
                seg.setQueryTimes(getLong(segmentMap, key));
                segmentTimes.add(seg);
            });
        });
        if(segmentTimes.size() > 0) {
            logTimesMapper.saveOrUpdateSegmentQueryTimes(segmentTimes);
        }
    }

    private Map<String, Map<String, Map<String, Long>>> queryTimes(List<KylinLog> logs){
        Map<String, Map<String, Map<String, Long>>> allTimes = new HashMap<>();

        logs.forEach(log -> {

            if (StringUtils.isNotEmpty(log.getCubeId())) {
                Map<String, Map<String, Long>> queryTimes = allTimes.get(log.getCubeId());
                if (queryTimes == null) {
                    queryTimes = new HashMap<>();
                    queryTimes.put(this.timesKey, new HashMap<>());
                    queryTimes.put(this.groupTimesKey, new HashMap<>());
                    queryTimes.put(this.filterTimesKey, new HashMap<>());
                    queryTimes.put(this.groupsTimesKey, new HashMap<>());
                    queryTimes.put(this.cuboidTimesKey, new HashMap<>());
                    queryTimes.put(this.lastQueryTimeKey, new HashMap<>());
                    queryTimes.put(this.segmentQueryTimeKey, new HashMap<>());
                    allTimes.put(log.getCubeId(), queryTimes);
                }
                queryTimes(queryTimes, log);
            }

        });
        return allTimes;
    }

    private void queryTimes(Map<String, Map<String, Long>> queryTimes, KylinLog log){

        if(log == null || log.getCubeId() == null || log.getCuboid() == null){
            return;
        }

        Map<String, Long> times = queryTimes.get(this.timesKey);
        Map<String, Long> groupTimes = queryTimes.get(this.groupTimesKey);
        Map<String, Long> filterTimes =queryTimes.get(this.filterTimesKey);
        Map<String, Long> groupsTimes =queryTimes.get(this.groupsTimesKey);
        Map<String, Long> cuboidTimes =queryTimes.get(this.cuboidTimesKey);
        Map<String, Long> lastQueryTime =queryTimes.get(this.lastQueryTimeKey);

        String groupList = log.getGroupList();
        List<String> temp = new ArrayList<>();

        if(StringUtils.isNotEmpty(groupList)){
            String[] groups = groupList.split(",");
            temp.addAll(Arrays.asList(groups));
            for(String group : groups){
                addOne(times, group);
                addOne(groupTimes, group);
                setMax(lastQueryTime, group, log.getQueryTime().getTime());
            }
        }

        String filterList = log.getFilterList();
        if(StringUtils.isNotEmpty(filterList)){
            String[] filters = filterList.split(",");
            for(String filter : filters){
                addOne(filterTimes, filter);
                if(temp.contains(filter)){
                    continue;
                }
                temp.add(filter);
                addOne(times, filter);
                setMax(lastQueryTime, filter, log.getQueryTime().getTime());
            }
        }
        addOne(times, this.totalKey);

        temp.sort((s1, s2) -> s1.compareTo(s2));
        String groupKey = StringUtils.join(temp, ",");
        addOne(groupsTimes, groupKey);

        addOne(cuboidTimes, log.getCuboid().toString());

        setMax(lastQueryTime, log.getCuboid().toString(), log.getQueryTime().getTime());
        setMax(lastQueryTime, "groups:"+groupKey, log.getQueryTime().getTime());
        setMax(lastQueryTime, log.getCubeId(), log.getQueryTime().getTime());

        Long duration = times.get(this.totalDuration);
        if(duration == null){
            duration = 0L;
        }
        duration += ((Double)(log.getDuration()*1000)).longValue();
        times.put(this.totalDuration, duration);

        /**
         * 处理segment统计
         */
        Map<String, Long> segmentQueryTimes =queryTimes.get(this.segmentQueryTimeKey);
        if(StringUtils.isNotEmpty(log.getSegmentId())){
            String segment = log.getSegmentId();
            String[] segmentIds = segment.split(",");
            for(String seg : segmentIds){
                addOne(segmentQueryTimes, seg);
            }
        }
    }

    private void setMax(Map<String, Long> map, String key, Long newValue){
        map.put(key, Math.max(getLong(map, key), newValue));
    }
    private void addOne(Map<String, Long> map, String key){
        map.put(key, getLong(map, key) + 1);
    }
    private Long getLong(Map<String, Long> map, String key){
        return map.containsKey(key) ? map.get(key) : 0L;
    }

}
